import def from '../../define'
import util from '../../util'
import timer from '../../timer'
import { popupToast } from '../../ui-util'

const makeupPlugin = require('../../pfMakeupPlugin/index')

const status = makeupPlugin.getStatusTable()
const app = getApp()

let pfMakeup
let that
let camera
let cameraFrameListener
let photoModeRawFrame
const log = t => getApp().globalData.logger.log('[tryOn]' + t)

let forceOpenCameraTimeout
let _camerafps
let _renderfps
let printFpsLoop

Page({
  data: {
    offscreenCanvasWidth: 0,
    offscreenCanvasHeight: 0,
    pixalRatio: 1,
    canvasWidth: 360,
    canvasHeight: 640,
    frameSize: 'medium',
    canvasHidden: false,
    enableCamera: true,
    renderingCanvasId: 'tryOnCanvas',
    makeupCameraId: 'tryOnCameraId',
    cameraFacing: 'front',

    infoText: '本技术由玩美彩妆支持',
    skuTypeList: {
      types: [],
      selected: null // {type: '', name: ''}
    },
    productList: {
      products: [],
      selectedGuid: ''
    },
    lookDetail: {
      isShow: false,
      look: {}
    },
    skuList: {
      skus: [],
      palettesMap: {},
      patternsMap: {},
      patternGuids: []
    },
    tryOnList: def.initTypesMap(null), // key: type, value: {productGuid: '', skuGuid: '', paletteGuid: '', patternGuid: ''}
    intensities: {},
    skuTypeIntensityState: {
      isSupport: false,
      isEnabled: false,
      sliderValue: 0
    },
    isCompareEnabled: false,
    supportScaleTypes: def.SCALE_TYPES,
    scaleType: 'CENTER_CROP',
    isSDKReady: false,
    isShotBlocked: false,
    featureSlider: {
      pickerOptions: def.SUPPORT_FEATURE_LIST,
      values: {
        zoomRatio: 0,
        comparePosition: 5,
        skinSmooth: 0,
        faceReshape: 0,
        enlargeEye: 0
      }
    },
    frameInfo: ''
  },

  createForceOpenCameraTimeout: function() {
    forceOpenCameraTimeout = setTimeout(() => {
      this.restartCameraFrameListener(() => {})
    }, 1000)
  },

  clearForceOpenCameraTimeout: function() {
    if (forceOpenCameraTimeout) {
      clearTimeout(forceOpenCameraTimeout)
      forceOpenCameraTimeout = null
    }
  },

  onLoad: function() {
    log('[onLoad]')
    that = this
    this.setData({
      frameSize: getApp().globalData.systemInfo.platform === 'Android' ? 'medium' : 'small'
    })

    // eslint-disable-next-line new-cap
    _camerafps = new timer()
    // eslint-disable-next-line new-cap
    _renderfps = new timer()

  },
  initTryOnButtons: function() {
    this.setData({
      skuTypeList: {
        ...this.data.skuTypeList,
        types: app.globalData.featureSetting.enableAllFeatures ? def.ALL_MAKEUP_TYPES : def.SUPPORT_LIST
      }
    })
  },

  canvasOnReady() {
    log('[canvasOnReady]')
    my.showLoading()
    const systemInfo = my.getSystemInfoSync()
    const makeupPlugin = require('../../pfMakeupPlugin/index')
    pfMakeup = makeupPlugin.getPfMakeup()
    this.setData(
      {
        pixelRatio: systemInfo.pixelRatio
      },
      () => {
        const timeoutMs = (getApp().globalData.systemInfo.platform === 'Android') ? 0 : 500
        setTimeout(() => {
          my.createCanvas({
            id: this.data.renderingCanvasId,
            success: c => {
              log(`createCanvas: ${JSON.stringify(c)}`)
              pfMakeup.init({
                perfectLib: app.globalData.perfectLib,
                canvas: c,
                callback: this.initCallback,
                enableSkia: true
              })
            }
          })
        }, timeoutMs)
      }
    )
  },

  onReady: function() {
    log('[onReady]')
  },
  onShow: function() {
    this.setData({
      isShotBlocked: false
    })
    this.initTryOnButtons()

    if (!that.data.enableCamera && photoModeRawFrame) {
      // Android-Taobao requires time to remount canvas when onShow
      app.globalData.systemInfo.platform === 'Android' && that.processCanvasTilCanvasMount()
    } else {
      cameraFrameListener && cameraFrameListener.start()
      this.createForceOpenCameraTimeout()
    }
  },
  onHide: function() {
    cameraFrameListener && cameraFrameListener.stop()
  },
  onUnload: function() {
    pfMakeup.unInit()
    that.clearForceOpenCameraTimeout()
    pfMakeup = null
    cameraFrameListener = null
    camera = null
    photoModeRawFrame = null
    clearInterval(printFpsLoop)
    printFpsLoop = null
  },
  processCanvasTilCanvasMount: function() {
    my.showLoading()
    setTimeout(() => {
      pfMakeup.processFrame(photoModeRawFrame, true, that.processPhotoFrameCallback)
    }, 3000)
  },
  initCallback: function(statusCode, extraInfo) {
    log('pfMakeup initcallback:' + statusCode)
    that.setInitData()
    that.onCameraModeClick()
    my.hideLoading()
    if (statusCode !== status.STATUS_SUCCESS) {
      my.alert({
        content: 'Init failed, statusCode=' + statusCode,
        success: () => my.navigateBack()
      })
    }
  },
  setInitData: function() {
    log('[setInitData]')
    const intensities = pfMakeup.getIntensities()
    this.setData({
      isSDKReady: true,
      intensities,
      featureSlider: {
        ...this.data.featureSlider,
        values: {
          ...this.data.featureSlider.values,
          skinSmooth: intensities.skinSmooth,
          faceReshape: intensities.faceReshape,
          enlargeEye: intensities.enlargeEye
        }
      }
    })
  },
  onSkuTypeClick: function(e) {
    const type = e.target.dataset.type
    const isTypeSelected = this.data.skuTypeList.selected && this.data.skuTypeList.selected.type === type
    if (isTypeSelected) {
      return this.resetProductSkuLists()
    }

    this.setSelectedSkuType(type)
    this.setSkuTypeIntensityState(type)
    this.getProductList(type)
  },
  onProductNoneClick: function() {
    const type = this.data.skuTypeList.selected.type
    if (!this.data.tryOnList[type]) return
    if (type === 'Look') {
      return this.clearTypeEffects().then(() => {
        this.setData({
          productList: {
            ...this.data.productList,
            selectedGuid: ''
          },
          lookDetail: {
            isShow: false,
            look: {}
          }
        })
        popupToast('Clear look')
      })
    }
    this.pfClearEffects([type])
      .then(() => {
        that.resetSkuList()
        that.setTryOnList(type, null)
        that.setSkuTypeIntensityState(type)
      })
      .catch((statusCode, error) => {
        popupToast('Clear failed')
      })
  },
  onProductClick: function(e) {
    const { productIdx, productGuid, skuType } = e.target.dataset
    log(`[onProductClick]${JSON.stringify({ productIdx, productGuid, skuType })}`)

    if (skuType === 'Look') {
      return this.clearTypeEffects().then(() => that.handleApplyLook(this.data.productList.products[productIdx]))
    }
    productGuid === this.data.productList.selectedGuid ? this.resetSkuList() : this.setStatesOnProductSelected(this.data.productList.products[productIdx])
  },
  handleApplyLook: function(look = {}) {
    if (!look.guid) return
    my.showLoading()

    that
      .pfApplyMakeupByLook(look.guid)
      .then(() => {
        log('[applyMakeupByLook] getProductIds: ' + JSON.stringify(pfMakeup.getProductIds()))
        log('[applyMakeupByLook] getEffectIds: ' + JSON.stringify(pfMakeup.getEffectIds()))
        that.setStatesOnProductSelected(look)
        that.setStatesOnMakeupApplied(that.data.skuTypeList.selected.type, { productGuid: look.guid })
        my.hideLoading()
      })
      .catch((statusCode, error) => {
        my.hideLoading()
        my.alert({
          content: `Apply look failed, error: ${error}`,
          success: () => that.onClearAllEffectsClick({ showSuccessToast: true })
        })
      })
  },
  setStatesOnProductSelected: function(product = {}) {
    if (product.type === 'Look') {
      return this.setData({
        lookDetail: {
          ...this.data.lookDetail,
          look: product
        },
        productList: {
          ...this.data.productList,
          selectedGuid: product.guid
        },
        skuList: {
          skus: [],
          patternsMap: {},
          palettesMap: {},
          patternGuids: []
        }
      })
    }

    product.skus && product.skus.length && this.appendPaletteGuidsToProductSkus(product)

    this.setData({
      productList: {
        ...this.data.productList,
        selectedGuid: product.guid
      },
      skuList: {
        skus: product.skus || [],
        patternsMap: product.patternsMap || {},
        palettesMap: product.palettesMap || {},
        patternGuids: product.type === 'skin_toner' ? [] : that.getPatternGuidsForSkuList(product)
      }
    })
  },
  appendPaletteGuidsToProductSkus: function(product) {
    product.skus = product.skus.map(sku => ({
      ...sku,
      paletteGuids: product.type === 'skin_toner' ? [sku.guid] : Object.keys(sku.patterns)
    }))
  },
  getPatternGuidsForSkuList: function(product = {}) {
    const applied = this.data.tryOnList[product.type]
    const appliedSku = applied && product.skus && product.skus.find(sku => sku.guid === applied.skuGuid)

    if (!applied || !appliedSku) {
      return []
    }
    return product.type === 'eye_contact' ? [applied.skuGuid] : appliedSku.patterns[applied.skuGuid]
  },
  onShowLookDetail: function() {
    this.setData({
      lookDetail: {
        ...this.data.lookDetail,
        isShow: true
      }
    })
  },
  onHideLookDetail: function() {
    this.setData({
      lookDetail: {
        ...this.data.lookDetail,
        isShow: false
      }
    })
  },
  onSkuClick: async function(e) {
    const { skuType, productGuid, skuIndex, paletteGuid } = e.target.dataset

    if (this.data.tryOnList.Look && this.data.tryOnList.Look.productGuid) {
      await this.clearTypeEffects()
        .then(() => {
          this.setData({
            lookDetail: {
              isShow: false,
              look: {}
            }
          })
        })
        .catch((statusCode, error) => popupToast('Clear failed'))
    }

    const sku = this.data.skuList.skus[skuIndex]
    await this.handleApplySku({
      type: skuType,
      productGuid,
      skuGuid: sku.guid,
      paletteGuid,
      patternGuid: this.getPatternGuid(skuType, sku, paletteGuid)
    })
    this.setData({
      skuList: {
        ...this.data.skuList,
        patternGuids: sku.patterns[paletteGuid]
      }
    })
  },
  getPatternGuid: function(type, sku, paletteGuid) {
    switch (type) {
      case 'eye_contact':
        return sku.guid
      case 'skin_toner':
        return ''
      default:
        return (sku.patterns[paletteGuid] && sku.patterns[paletteGuid][0]) || ''
    }
  },
  onPatternClick: async function(e) {
    const { skuType, patternGuid } = e.target.dataset
    const { productGuid, skuGuid, paletteGuid } = this.data.tryOnList[skuType]
    log(`[onPatternClick]${JSON.stringify({ skuType, patternGuid, productGuid, skuGuid, paletteGuid })}`)
    await this.handleApplySku({
      type: skuType,
      productGuid,
      skuGuid,
      paletteGuid,
      patternGuid
    })
  },
  setStatesOnMakeupApplied: function(type, { productGuid = '', skuGuid = '', paletteGuid = '', patternGuid = '' }) {
    if (type === 'Look') {
      this.clearTryOnList()
    }
    this.setTryOnList(type, { productGuid, skuGuid, paletteGuid, patternGuid })
    this.setSkuTypeIntensityState(type)
  },
  onClearAllEffectsClick: function({ showSuccessToast = true } = {}) {
    return this.pfClearAllEffects()
      .then(() => {
        this.clearTryOnList()
        this.resetIntensityStates()
        this.resetProductSkuLists()
        showSuccessToast && popupToast('Clear all makeup')
      })
      .catch((statusCode, error) => {
        popupToast('Clear failed')
      })
  },
  onCameraSwitch: function() {
    const newFacing = that.data.cameraFacing === 'front' ? 'back' : 'front'

    cameraFrameListener.stop({
      success: () => {
        that.setData({ cameraFacing: newFacing }, () => {
          cameraFrameListener.start({ success: () => popupToast(`Switch to ${newFacing} camera`) })
        })
      }
    })
  },
  restartCameraFrameListener: function(callback) {
    cameraFrameListener &&
      cameraFrameListener.stop({
        success: () => cameraFrameListener.start({ success: callback })
      })
  },
  onShotClick: function() {
    this.setData(
      {
        isShotBlocked: true
      },
      () => {
        pfMakeup.takePicture(frame => {
          if (frame) {
            app.globalData.takePicture.frame = frame
            my.navigateTo({
              url: '/pages/takePicResult/takePicResult'
            })
          }
        })
      }
    )
  },
  onScaleTypeChange: function(e) {
    pfMakeup.setRendererConfig({
      scaleType: e.target.dataset.type
    })
    this.setData({
      scaleType: e.target.dataset.type
    })
  },
  onCameraModeClick: function() {
    photoModeRawFrame = null
    this.setData(
      {
        enableCamera: true
      },
      () => {
        camera = my.createCameraContext(this.data.makeupCameraId)
        cameraFrameListener = camera.onCameraFrame(frame => {
          _camerafps.end()
          // [Workaround][Android 9.7.1] If frame start normally, stop force start timeout
          this.clearForceOpenCameraTimeout()
          pfMakeup && pfMakeup.processFrame(frame, false, that.processCameraFrameCallback)
        })
        cameraFrameListener.start()
        // [Workaround][Android 9.7.1] Force open camera frame listener when frame didn't start to send correctly.
        this.createForceOpenCameraTimeout()
      }
    )
  },
  disableCameraMode: function() {
    this.setData({ enableCamera: false })
    cameraFrameListener && cameraFrameListener.stop()
    cameraFrameListener = null
    clearInterval(printFpsLoop)
    printFpsLoop = null
  },
  onPhotoModeClick: async function() {
    const res = await util.chooseImage({
      sourceType: ['album'],
      count: 1,
    })
    
    if (!res.apFilePaths || !res.apFilePaths[0]) {
      return
    }
    my.showLoading()
    that.disableCameraMode()

    const frame = await that.decodeImageByLib(res.apFilePaths[0])
    photoModeRawFrame = frame
    pfMakeup.processFrame(frame, true, res => that.processPhotoFrameCallback(res, true))
  },

  processCameraFrameCallback: function(res) {
    const status = makeupPlugin.getStatusTable()
    if (!res || res.status !== status.STATUS_SUCCESS) {
      return
    }

    _renderfps.end()
    if (!printFpsLoop) {
      printFpsLoop = setInterval(() => {
        that.setData({
          frameInfo: `cameraFps: ${_camerafps.fps().toFixed(0)} renderFps: ${_renderfps.fps().toFixed(2)}`
        })
      }, 1000)
    }
  },

  processPhotoFrameCallback: function(res, showAlert = false) {
    const status = makeupPlugin.getStatusTable()
    if (res.status !== status.STATUS_SUCCESS && showAlert) {
      my.alert({
        content: 'Face detection failed!'
      })
    }
    my.hideLoading()
  },

  decodeImageByCanvas: async function(filePath) {
    const { width, height } = await util.getImageInfo({ src: filePath })
    await this.setData({
      offscreenCanvasWidth: width,
      offscreenCanvasHeight: height
    })

    const ctx = my.createCanvasContext('offScreenCanvas')
    ctx.drawImage(filePath, 0, 0, width, height)
    await util.actuallyDrawOnCanvas(ctx)
    return util.getImageDataFromCanvas(ctx, { x: 0, y: 0, width, height })
  },
  decodeImageByLib: async function(filePath) {
    const fs = my.getFileSystemManager()
    const file = await fs.readFileSync({ filePath })
    const decode = require('../../jpegDecoder')

    return decode(file.data)
  },
  onFeatureSliderValueChange: function({ type, value }) {
    this.updateIntensity(value, false, type)
  },
  onFeatureSliderValueChanging: function({ type, value }) {
    this.updateIntensity(value, true, type)
  },
  onIntensityChange: function(e) {
    this.updateIntensity(e.detail.value, false, this.data.skuTypeList.selected.type)
  },
  onIntensityChanging: function(e) {
    this.updateIntensity(e.detail.value, true, this.data.skuTypeList.selected.type)
  },
  updateIntensity: function(value, throttle = false, key) {
    switch (key) {
      case def.FEATURE.ZOOM_RATIO.type:
        pfMakeup.setRendererConfig({
          zoomRatio: 1 + value * 0.01
        })
        break
      case def.FEATURE.COMPARE_POSITION.type:
        pfMakeup.setComparePosition(value * 0.1)
        break
      default:
        if (!this.getIsSupportIntensity(key)) break
        const updatedIntensity = {}
        updatedIntensity[key] = value
        pfMakeup.setIntensities(updatedIntensity)
    }

    if (!throttle) {
      const sliderValues = { ...this.data.featureSlider.values }
      sliderValues[key] = value
      this.setData({
        featureSlider: {
          ...this.data.featureSlider,
          values: sliderValues
        }
      })
      this.updateIntensityStates(key, value)
    }
  },
  updateIntensityStates: function(key, value) {
    if (!this.getIsSupportIntensity(key)) return
    switch (key) {
      case def.FEATURE.SKIN_SMOOTH.type:
      case def.FEATURE.FACE_RESHAPE.type:
      case def.FEATURE.ENLARGE_EYE.type:
        const intensities = { ...this.data.intensities }
        intensities[key] = value
        this.setData({ intensities })
        break
      default:
        const newIntensities = { ...this.data.intensities }
        newIntensities[key] = value
        this.setData({
          intensities: newIntensities,
          skuTypeIntensityState: {
            ...this.data.skuTypeIntensityState,
            sliderValue: value
          }
        })
    }
  },
  onCompareClick: function() {
    this.data.isCompareEnabled ? this.disableComparison() : this.enableComparison()
  },
  disableComparison: function() {
    pfMakeup.enableComparison(false)
    this.setData({
      isCompareEnabled: false
    })
    popupToast('Turn off compare mode')
  },
  enableComparison: function() {
    pfMakeup.enableComparison(true)
    this.setData({
      isCompareEnabled: true
    })
    popupToast('Turn on compare mode')
  },
  getProductList: function(type) {
    my.showLoading()
    this.getAllLooksOrProducts(type, products => {
      log(`[getProductList]productsLength=${products && products.length}`)
      that.setData(
        {
          productList: {
            products,
            selectedGuid: ''
          }
        },
        () => my.hideLoading()
      )
    })
  },
  getValueFromTypesMap: function(key, type) {
    return this.data[key] && this.data[key][type]
  },
  getIsSupportIntensity: function(type) {
    return def.SUPPORT_INTENSITY_LIST.find(skuType => skuType.type === type)
  },
  setTryOnList: function(type, tryOnObj = null) {
    const newTryOnList = { ...this.data.tryOnList }
    newTryOnList[type] = tryOnObj

    this.setData({ tryOnList: newTryOnList })
  },
  clearTryOnList: function() {
    this.setData({ tryOnList: def.initTypesMap(null) })
  },
  setSkuTypeIntensityState: function(type) {
    const isTypeSupport = this.getIsSupportIntensity(type)
    const isSkuApplied = this.getValueFromTypesMap('tryOnList', type)
    const intensities = pfMakeup.getIntensities()
    log(`[setSkuTypeIntensityState]${JSON.stringify({ type, intensities })}`)

    this.setData({
      skuTypeIntensityState: {
        isSupport: isTypeSupport,
        isEnabled: isTypeSupport && isSkuApplied,
        sliderValue: intensities[type]
      }
    })
  },
  resetIntensityStates: function() {
    const intensities = pfMakeup.getIntensities()

    this.setData({
      intensities,
      skuTypeIntensityState: {
        isSupport: false,
        isEnabled: false,
        sliderValue: 0
      },
      featureSlider: {
        ...this.data.featureSlider,
        values: {
          ...this.data.featureSlider.values,
          skinSmooth: intensities.skinSmooth,
          faceReshape: intensities.faceReshape,
          enlargeEye: intensities.enlargeEye
        }
      }
    })
  },
  setSelectedSkuType: function(type) {
    const skuType = this.data.skuTypeList.types.find(st => st.type === type)
    this.setData({
      skuTypeList: {
        ...this.data.skuTypeList,
        selected: skuType || { type, name: type }
      }
    })
  },
  resetSkuList: function() {
    this.setData({
      productList: {
        ...this.data.productList,
        selectedGuid: ''
      },
      skuList: {
        skus: [],
        patternsMap: {},
        palettesMap: {},
        patternGuids: []
      }
    })
  },
  resetProductSkuLists: function() {
    this.setData({
      skuTypeList: {
        ...this.data.skuTypeList,
        selected: null
      },
      productList: {
        products: [],
        selectedGuid: ''
      },
      lookDetail: {
        isShow: false,
        look: {}
      },
      skuList: {
        skus: [],
        palettesMap: {},
        patternsMap: {},
        patternGuids: []
      }
    })
  },
  getAllLooksOrProducts: function(type, callback) {
    const OFFSET = 20
    let allProducts = []
    let start = 0

    const goNextRequestOrCallback = products => {
      if (!Array.isArray(products)) {
        return callback(allProducts)
      }

      allProducts = [...allProducts, ...products]
      if (!products.length || products.length < OFFSET) {
        return callback(allProducts)
      }

      start = start + OFFSET
      that.getLooksOrProducts(type, start, goNextRequestOrCallback)
    }

    that.getLooksOrProducts(type, start, goNextRequestOrCallback)
  },
  getLooksOrProducts: function(type, start, callback) {
    type === 'Look' ? pfMakeup.getLooks(start, callback) : pfMakeup.getListByEffect(type, start, callback)
  },
  handleApplySku: function({ type, productGuid, skuGuid, paletteGuid, patternGuid }) {
    const sku = { productGuid, skuGuid }
    paletteGuid && (sku.paletteGuid = paletteGuid)
    patternGuid && (sku.patternGuid = patternGuid)

    return that
      .pfApplyMakeupBySkus([sku])
      .then(() => {
        that.setStatesOnMakeupApplied(type, sku)
        log('[handleApplySku] getProductIds: ' + JSON.stringify(pfMakeup.getProductIds()))
        log('[handleApplySku] getEffectIds: ' + JSON.stringify(pfMakeup.getEffectIds()))
      })
      .catch((statusCode, error) => {
        popupToast('Apply failed')
      })
  },
  clearTypeEffects: function() {
    const types = []
    this.data.skuTypeList.types.forEach(({ type }) => {
      type && type !== 'Look' && types.push(type)
    })
    log(`clearTypeEffects: ${JSON.stringify(types)}`)

    return this.pfClearEffects(types).then(() => that.clearTryOnList())
  },
  pfApplyMakeupBySkus: function(skus) {
    return new Promise((resolve, reject) => {
      pfMakeup.applyMakeupBySkus(skus, (statusCode, error) => {
        if (statusCode === status.STATUS_SUCCESS) {
          log(`[pfApplyMakeupBySkus]success: ${JSON.stringify(skus)}`)
          return resolve()
        }
        log(`[pfApplyMakeupBySkus]fail: ${JSON.stringify({ skus, statusCode, error })}`)
        reject(statusCode, error)
      })
    })
  },
  pfApplyMakeupByLook: function(lookGuid) {
    return new Promise((resolve, reject) => {
      pfMakeup.applyMakeupByLook({ lookGuid }, (statusCode, error) => {
        if (statusCode === status.STATUS_SUCCESS) {
          log(`[pfApplyMakeupByLook]success: ${JSON.stringify({ lookGuid })}`)
          return resolve()
        }
        log(`[pfApplyMakeupByLook]fail: ${JSON.stringify({ lookGuid, statusCode, error })}`)
        reject(statusCode, error)
      })
    })
  },
  pfClearEffects: function(types) {
    return new Promise((resolve, reject) => {
      pfMakeup.clearEffects(types, (statusCode, error) => {
        if (statusCode === status.STATUS_SUCCESS) {
          return resolve()
        }
        log(`[pfClearEffects]fail: ${JSON.stringify({ statusCode, error })}`)
        reject(statusCode, error)
      })
    })
  },
  pfClearAllEffects: function() {
    return new Promise((resolve, reject) => {
      pfMakeup.clearAllEffects((statusCode, error) => {
        if (statusCode === status.STATUS_SUCCESS) {
          return resolve()
        }
        log(`[pfClearAllEffects]fail: ${JSON.stringify({ statusCode, error })}`)
        reject(statusCode, error)
      })
    })
  }
})
